package proj2501.simple_module;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.List;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

import proj2501.util.UU;

class P012Rhino {
	public P012Rhino() {
		test01();
		test02();
	}
	private static void test01() {
		// From Java Scripting Programmer's Guide
		
		System.out.println("Rhino");
		
		ScriptEngineManager manager = new ScriptEngineManager();
		List<ScriptEngineFactory> list = manager.getEngineFactories();
		for (ScriptEngineFactory factory : list) {
			System.out.println("Engine Name: " + factory.getEngineName());
			System.out.println("Engine Version: " + factory.getEngineVersion());
			System.out.println("Language Name: " + factory.getLanguageName());
			System.out.println("Language Version: " + factory.getLanguageVersion());
			System.out.println();
		}
		ScriptEngine engine = manager.getEngineByName("JavaScript");
		
		// Examples hello world.
		try {
			engine.eval("println('Hello, World')");
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		
		// Evaluating a script file.
		String path ="Rhino Test 01.js"; 
		String text01 = "println('This is hello from test.js');";
		UU.write(path, text01);
		Reader reader;
		try {
			reader = new FileReader(path);
		} catch (FileNotFoundException e) {
			throw new RuntimeException();
		}
		try {
			try {
				engine.eval(reader);
			} catch (ScriptException e) {
				throw new RuntimeException();
			}
		} finally {
			try {
				reader.close();
			} catch (IOException e) {
				throw new RuntimeException();
			}
		}
		new File(path).delete();
		
		// Script variable.
		engine.put("file01", new File("test.txt"));
		try {
			engine.eval("println(file01.getAbsolutePath())");
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		
		// Invoking a script function.
		String script = "function hello(name) { println('Hello, ' + name); }";
		try {
			engine.eval(script);
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		if (! (engine instanceof Invocable)) {
			throw new RuntimeException();
		}
		Invocable invocable = (Invocable) engine;
		try {
			for (int i = 0; i < 3; i++) {
				invocable.invokeFunction("hello", "Scripting!!");
			}
		} catch (NoSuchMethodException e) {
			throw new RuntimeException();
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		
		// Invoking a script method.
		script = "var obj = new Object(); obj.hello = function(name) { println('Hello, ' + name); }";
		try {
			engine.eval(script);
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		Object scriptObject = engine.get("obj");
		try {
			invocable.invokeMethod(scriptObject, "hello", "Script Method !!");
		} catch (NoSuchMethodException e) {
			throw new RuntimeException();
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		
		// Implementing a Java interface by script.
		script = "function run() { println('run called'); }";
		try {
			engine.eval(script);
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		Runnable runnbale = invocable.getInterface(Runnable.class);
		Thread thread = new Thread(runnbale);
		thread.start();
		
		// Implementing a Java interface by a script object.
		script = "var obj = new Object(); obj.run = function() { println('run method called'); }";
		try {
			engine.eval(script);
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		scriptObject = engine.get("obj");
		runnbale = invocable.getInterface(scriptObject, Runnable.class);
		thread = new Thread(runnbale);
		thread.start();
		
		// Multiple scopes for scripts.
		engine.put("x", "hello");
		try {
			engine.eval("println(x);");
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		ScriptContext context = new SimpleScriptContext();
		Bindings engineScope = context.getBindings(ScriptContext.ENGINE_SCOPE);
		engineScope.put("x", "world");
		try {
			engine.eval("println(x);", context);
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		
		// Importing Java packages and classes.
		script = "importPackage(java.awt);" +
				"importClass(java.awt.Frame);" +
				"var frame = new java.awt.Frame('Title');" +
				"frame.setVisible(true);" +
				"println(frame.title);" +
				"" +
				"var SwingGui = new JavaImporter(javax.swing, javax.swing.event, javax.swing.border, java.awt.event);" +
				"with (SwingGui) {" +
				"  var mybutton = new JButton('button name');" +
				"  var myframe = new JFrame('frame name');" +
				"  println(mybutton.getText());" +
				"}";
		try {
			engine.eval(script);
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
	}
	private static void test02() {
		// Reference (ja): http://www.techscore.com/tech/Java/JavaSE/JavaSE6/5/
		
		ScriptEngineManager manager = new ScriptEngineManager();
		ScriptEngine engine = manager.getEngineByName("JavaScript");
		engine.put("v1", 1111);
		engine.put("v2", 2222);
		String script = "(function() { return v1 * v2; })();";
		int limit = 10000;
		long start, end;
		
		start = System.currentTimeMillis();
		for (int i = 0; i < limit; i++) {
			try {
				engine.eval(script);
			} catch (ScriptException e) {
				throw new RuntimeException();
			}
		}
		end = System.currentTimeMillis();
		long elapsed1 = end - start;
		
		start = System.currentTimeMillis();
		if (! (engine instanceof Compilable)) {
			throw new RuntimeException();
		}
		Compilable compilable = (Compilable) engine;
		CompiledScript compiledScript;
		try {
			compiledScript = compilable.compile(script);
		} catch (ScriptException e) {
			throw new RuntimeException();
		}
		for (int i = 0; i < limit; i++) {
			try {
				compiledScript.eval();
			} catch (ScriptException e) {
				throw new RuntimeException();
			}
		}
		end = System.currentTimeMillis();
		long elapsed2 = end - start;
		System.out.println("compiled / interpreted: " + elapsed2 + " / " + elapsed1 + " (" + (double) elapsed2 / elapsed1 + ")");
	}
}
